﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Inovout.Data.Repository.Linq
{
    internal class FilterItemExpression : IComparable<FilterItemExpression>, IComparable
    {
        public int Index { get; private set; }
        public string Name { get; private set; }
        public FilterOperation Operation { get; private set; }
        public FilterItemExpression(int index, string name, FilterOperation operation)
        {
            this.Index = index;
            this.Name = name;
            this.Operation = operation;
            HashCodeCombiner hashCodeCombiner = new HashCodeCombiner();
            hashCodeCombiner.Add(this.Name);
            hashCodeCombiner.Add(this.Operation);
            hashCode = hashCodeCombiner.CombinedHash;
        }
        private static Dictionary<Type, MethodInfo> convertMethods;
        static FilterItemExpression()
        {
            convertMethods = new Dictionary<Type, MethodInfo>();
            var methods = typeof(Convert).GetMethods().Where(m => m.Name.StartsWith("To") && !m.Name.EndsWith("String") &&
                m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(string));

            convertMethods = methods.ToDictionary(m => m.ReturnType);
        }
        internal Expression BuildValueExpression(Expression valuesExpression, Type type)
        {
            Expression indexExpression = Expression.Constant((object)Index, typeof(int));
            Expression valueExpression = Expression.ArrayIndex(
                    valuesExpression,
                    indexExpression
                );
            MethodInfo convertMethod = null;
            Type checkedType = Nullable.GetUnderlyingType(type) ?? type;
            if (convertMethods.TryGetValue(type, out convertMethod))
            {
                return Expression.Call(convertMethod, valueExpression);
            }
            else if (type != checkedType)
            {
                return Expression.Convert(Expression.Call(convertMethods[checkedType], valueExpression), type);
            }

            return valueExpression;
        }
        private readonly int hashCode;
        public override int GetHashCode()
        {
            return hashCode;
        }

        public int CompareTo(FilterItemExpression other)
        {
            FilterItemExpression that = other as FilterItemExpression;
            if (that == null)
            {
                return -1;
            }
            int result = this.Name.CompareTo(that.Name);
            if (result == 0)
            {
                return this.Operation.CompareTo(that.Operation);
            }
            else
            {
                return result;
            }
        }

        public int CompareTo(object obj)
        {
            return this.CompareTo(obj as FilterItemExpression);
        }
    }
}
